package com.swt.gap.service.impl;

import com.swt.gap.common.ConditionTypeEnum;
import com.swt.gap.common.constant.GraphDbConstant;
import com.swt.gap.common.NodeTypeEnum;
import com.swt.gap.common.graph.EmbeddedNeo4j;
import com.swt.gap.common.graph.GraphNeo4jVO;
import com.swt.gap.common.graph.GraphProcessor;
import com.swt.gap.model.params.PropertyFilterConfig;
import com.swt.gap.model.params.SearchNodeByProperty;
import com.swt.gap.model.vo.graph.ExpendMenuVO;
import com.swt.gap.model.vo.graph.NetworkAnalyseVO;
import com.swt.gap.model.vo.graph.PathAnalyzeVO;
import com.swt.gap.service.GraphService;
import org.neo4j.graphdb.*;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * 请填写说明
 *
 * @author sx
 * @date 2020/5/13 15:16
 */
@Service
public class GraphServiceImpl implements GraphService {

    private static final Integer MIN_DEPTH = 2;
    private static final Integer MAX_DEPTH = 6;

    private final GraphDatabaseService graphDatabaseService;
    private final GraphProcessor graphProcessor;

    public GraphServiceImpl(EmbeddedNeo4j embeddedNeo4j, GraphProcessor graphProcessor) {
        this.graphDatabaseService = embeddedNeo4j.getGraphDatabaseService();
        this.graphProcessor = graphProcessor;
    }

    @Override
    public List<GraphNeo4jVO.Node> listNodeByName(String keyword, Integer taskId) {

//        String cypher = "match (n) where n.taskId =" + taskId + " and n.name CONTAINS '" + keyword + "' return n";
        String cypher = "CALL db.index.fulltext.queryNodes(\"all\", \"" + keyword
                + "\") YIELD node, score  where node.taskId = " + taskId + " RETURN node as n order by score desc";

        GraphNeo4jVO graph = graphProcessor.getNodeByCypher(cypher);
        return graph.getNodes();
    }

    @Override
    public GraphNeo4jVO getPath(PathAnalyzeVO pathAnalyzeVO) {
        //如未指定深度，则默认给6
        int depth = MAX_DEPTH;
        if (null != pathAnalyzeVO.getDepth()) {
            depth = pathAnalyzeVO.getDepth();
        }
        String cypher;
        if (pathAnalyzeVO.getIsShortest()) {
            cypher = "match p=allShortestPaths((m{taskId:" + pathAnalyzeVO.getTaskId() +
                    "})-[*.." + depth +
                    "]-(n{taskId:" + pathAnalyzeVO.getTaskId() + "})) where m.id='"
                    + pathAnalyzeVO.getListIds().get(0) +
                    "' and n.id='" + pathAnalyzeVO.getListIds().get(1) + "' return p";
        } else {
            cypher = "match p=(m{taskId:" + pathAnalyzeVO.getTaskId() +
                    "})-[*.." + depth +
                    "]-(n{taskId:" + pathAnalyzeVO.getTaskId() + "}) where m.id='"
                    + pathAnalyzeVO.getListIds().get(0) +
                    "' and n.id='" + pathAnalyzeVO.getListIds().get(1) + "' return p";
        }

        return graphProcessor.getPathByCypher(cypher);
    }


    @Override
    public List<NetworkAnalyseVO> activityAnalyse(Integer taskId) {
        List<NetworkAnalyseVO> retValue = new ArrayList<>();
        String cypher = "MATCH (n)-[]-() with count(n)  AS score, n where n.taskId=" + taskId +
                " return n.id as id, n as node,  score order by score  desc limit 10";
        try (Transaction tx = graphDatabaseService.beginTx()) {
            Result result = graphDatabaseService.execute(cypher);
            result.forEachRemaining(r -> {
                Node node = (Node) r.get("node");
                GraphNeo4jVO.Node retNode = graphProcessor.transferDbNodeToCustomNode(node);
                NetworkAnalyseVO score = new NetworkAnalyseVO((String)r.get("id"), retNode, ((Long)r.get("score")).doubleValue());
                retValue.add(score);
            });


        }
        return retValue;
    }

    @Override
    public List<NetworkAnalyseVO> importanceAnalyse(Integer taskId) {
        List<NetworkAnalyseVO> retValue = new ArrayList<>();
        try (Transaction tx = graphDatabaseService.beginTx()) {

//            String cypher =
//                    "CALL algo.pageRank.stream($label, $relationship, $config) YIELD nodeId, score  " +
//                            " MATCH (node) where id(node) = nodeId  and node.taskId = " + taskId + " RETURN node.id as id, node, score ORDER BY score DESC limit 10;";

//            Map<String, Object> params = new HashMap<>(3);
//            Map<String, Object> config = new HashMap<>();
//            Map<String, Object> configParams = new HashMap<>();
//            configParams.put("taskId", taskId);
//            config.put("params", configParams);
//            params.put( "label", "");
//            params.put( "relationship", "");
//            params.put( "config", config);
//            Result result = graphDatabaseService.execute(cypher, params);
            String cypher = "CALL algo.pageRank.stream('', '', {taskId:" + taskId + "}) YIELD nodeId, score  " +
                    " MATCH (node) where id(node) = nodeId  and node.taskId = " + taskId + " RETURN node.id as id, node, score ORDER BY score DESC limit 10;";

            Result result = graphDatabaseService.execute(cypher);
            result.forEachRemaining(res -> {
                Node node = (Node) res.get("node");
                GraphNeo4jVO.Node retNode = graphProcessor.transferDbNodeToCustomNode(node);
                NetworkAnalyseVO score = new NetworkAnalyseVO((String)res.get("id"), retNode, (double)res.get("score"));
                retValue.add(score);
            });
        }
        return retValue;
    }

    @Override
    public List<NetworkAnalyseVO> betweennessAnalyse(Integer taskId) {
        List<NetworkAnalyseVO> retValue = new ArrayList<>();
        try (Transaction tx = graphDatabaseService.beginTx()) {
            String cypher = "CALL algo.betweenness.stream($label, $relationship, $config) YIELD nodeId, centrality " +
                    " MATCH (node) where id(node) = nodeId and node.taskId = " + taskId + " RETURN node.id AS id, node, centrality ORDER BY centrality DESC limit 10;";
            Map<String, Object> config = new HashMap<>();
            Map<String, Object> configParams = new HashMap<>();
            configParams.put("taskId", taskId);

            config.put("params", configParams);
            Map<String, Object> params = new HashMap<>(3);
            params.put( "label", "");
            params.put( "relationship", "");
            params.put( "config", config);
            Result result = graphDatabaseService.execute(cypher, params);
//            String cypher = "CALL algo.betweenness.stream('', '', {taskId:" + taskId + "}) YIELD nodeId, centrality " +
//                    " MATCH (node) where id(node) = nodeId and node.taskId = " + taskId + " RETURN node.id AS id, node, centrality ORDER BY centrality DESC limit 10;";
//            Result result = graphDatabaseService.execute(cypher);
            result.forEachRemaining(res -> {
                Node node = (Node) res.get("node");
                GraphNeo4jVO.Node retNode = graphProcessor.transferDbNodeToCustomNode(node);
                NetworkAnalyseVO score = new NetworkAnalyseVO((String)res.get("id"), retNode, (double)res.get("centrality"));
                retValue.add(score);
            });
        }
        return retValue;
    }


    @Override
    public GraphNeo4jVO expendByNodes(String[] nodeIds, Integer taskId, Integer menuTypeCode, Integer pageNum, Integer pageSize) {
        if (null == pageNum) {
            pageNum = 1;
        }
        if (null == pageSize) {
            pageSize = 10000;
        }
        GraphNeo4jVO retValue = new GraphNeo4jVO();
        if (!NodeTypeEnum.EXTERNAL.getCode().equals(menuTypeCode)) {
            String label = NodeTypeEnum.getNameByCode(menuTypeCode);
            String labelCypher = "";
            if (!StringUtils.isEmpty(label)) {
                labelCypher = ":" + label;
            }
            String cypher = "";
            if (nodeIds.length == 1) {
                cypher = "Match p=(n{taskId:" + taskId + ", id:'" + nodeIds[0] + "'})-[]-(m" + labelCypher +
                        "{taskId:" + taskId + "}) return p order by m.name skip " + (pageNum -1) * pageSize +
                        " limit " + pageSize;
            } else if (nodeIds.length > 1) {
                StringBuilder sbCypher = new StringBuilder();
                StringBuilder sbIds = new StringBuilder();
                sbIds.append("[");
                for (int i = 0; i < nodeIds.length; i++) {
                    sbIds.append("'").append(nodeIds[i]).append("'");
                    if (i < nodeIds.length - 1) {
                        sbIds.append(",");
                    }
                }
                sbIds.append("]");
                sbCypher.append("match p=(n)-[]-(k")
                        .append(labelCypher)
                        .append(")-[]-(m) where n.taskId = ")
                        .append(taskId)
                        .append(" and m.taskId = ")
                        .append(taskId)
                        .append(" and n.id in ")
                        .append(sbIds)
                        .append(" and m.id in ")
                        .append(sbIds)
                        .append(" and m<>n    return p ")
                        .append(" order by k.name skip ")
                        .append((pageNum -1) * pageSize)
                        .append(" limit ")
                        .append(pageSize);

                cypher = sbCypher.toString();
            }

            if (!StringUtils.isEmpty(cypher)) {
                retValue = graphProcessor.getPathByCypher2(cypher);
            }
        }
        return retValue;
    }

    @Override
    public GraphNeo4jVO unmergeEvent(String startNodeId, String endNodeId, Integer taskId) {
        String firstLabel = NodeTypeEnum.EVENT.getName();
        String cypher = "Match p1=(n1{taskId:" +  taskId
                + ",id:'" + startNodeId + "'})-[]-(m:" + firstLabel + ") With m,p1 Match p2=(n2{taskId:" +  taskId
                + ",id:'" + endNodeId + "'})-[]-(m:" + firstLabel + ") return p1, p2 order by m.name desc ";
        GraphNeo4jVO retValue = graphProcessor.getPathByCypher2(cypher);
        /*List<GraphNeo4jVO.Node> nodes = retValue.getNodes();
        nodes = nodes.stream().sorted(Comparator.comparing(node ->
            (Double)node.getProperties().get(GraphDbConstant.PROPERTY_AMOUNT)
        )).collect(Collectors.toList());
        Collections.reverse(nodes);
        retValue.setNodes(nodes);*/
        return retValue;
    }

    @Override
    public List<NetworkAnalyseVO> indegreeCount(Integer taskId) {
        List<NetworkAnalyseVO> retValue = new ArrayList<>();
        String cypher = "MATCH (n{taskId:" + taskId + "}) WITH size((n)<--()) AS score, n where n.taskId=" + taskId +
                " return n.id as id, n as node, score order by score desc limit 10";
        try (Transaction tx = graphDatabaseService.beginTx()) {
            Result result = graphDatabaseService.execute(cypher);
            result.forEachRemaining(r -> {
                Node node = (Node) r.get("node");
                GraphNeo4jVO.Node retNode = graphProcessor.transferDbNodeToCustomNode(node);
                NetworkAnalyseVO score = new NetworkAnalyseVO((String)r.get("id"), retNode, ((Long)r.get("score")).doubleValue());
                retValue.add(score);
            });
        }
        return retValue;
    }

    @Override
    public List<NetworkAnalyseVO> outdegreeCount(Integer taskId) {
        List<NetworkAnalyseVO> retValue = new ArrayList<>();
        String cypher = "MATCH (n{taskId:" + taskId + "}) WITH size((n)-->()) AS score, n where n.taskId=" + taskId +
                " return n.id as id, n as node, score order by score desc limit 10";
        try (Transaction tx = graphDatabaseService.beginTx()) {
            Result result = graphDatabaseService.execute(cypher);
            result.forEachRemaining(r -> {
                Node node = (Node) r.get("node");
                GraphNeo4jVO.Node retNode = graphProcessor.transferDbNodeToCustomNode(node);
                NetworkAnalyseVO score = new NetworkAnalyseVO((String)r.get("id"), retNode, ((Long)r.get("score")).doubleValue());
                retValue.add(score);
            });
        }
        return retValue;
    }

    @Override
    public Set<String> listLabelsByTaskId(Integer taskId) {
        String cypher = "MATCH (n{taskId:" + taskId + "}) RETURN distinct labels(n) as labels";
        Set<String> labels = graphProcessor.getLabelsByCypher(cypher);
        // 去掉一级label
        for (NodeTypeEnum nodeTypeEnum : NodeTypeEnum.values()) {
            labels.remove(nodeTypeEnum.getName());
        }
        return labels;
    }

    @Override
    public Set<String> listKeysByLabel(Integer taskId, String label) {
        String cypher = "MATCH (n{taskId:" + taskId + "}) RETURN distinct keys(n) as keys";
        Set<String> keys = graphProcessor.getKeysByCypher(cypher);
        keys.remove(GraphDbConstant.PROPERTY_KEY_ID);
        keys.remove(GraphDbConstant.PROPERTY_KEY_SOURCE);
        keys.remove(GraphDbConstant.PROPERTY_KEY_ICON);
        keys.remove(GraphDbConstant.PROPERTY_KEY_TASK_ID);
        keys.remove(GraphDbConstant.PROPERTY_KEY_TIMES);
        return keys;
    }



    @Override
    public List<GraphNeo4jVO.Node> listNodeByProperty(SearchNodeByProperty property) {
        StringBuilder sbCypher = new StringBuilder();
        sbCypher.append("MATCH (n) WHERE n.taskId=")
                .append(property.getTaskId())
                .append(" ");

        for (PropertyFilterConfig propertyFilterConfig : property.getPropertyFilterConfigs()) {
            if (StringUtils.isEmpty(propertyFilterConfig.getPropertyKey()) || StringUtils.isEmpty(propertyFilterConfig.getConditionType())) {
                break;
            }
            sbCypher.append(" and ");
            ConditionTypeEnum conditionTypeEnum = ConditionTypeEnum.getByCode(propertyFilterConfig.getConditionType());
            switch (conditionTypeEnum) {
                case GREATER:
                    sbCypher.append("n.").append(propertyFilterConfig.getPropertyKey()).append(" > ").append(propertyFilterConfig.getValue());
                    break;
                case LESS_THAN:
                    sbCypher.append("n.").append(propertyFilterConfig.getPropertyKey()).append(" < ").append(propertyFilterConfig.getValue());
                    break;
                case EQUAL:
                    sbCypher.append("n.").append(propertyFilterConfig.getPropertyKey()).append(" = ").append(propertyFilterConfig.getValue());
                    break;
                case LIKE:
                    sbCypher.append("n.").append(propertyFilterConfig.getPropertyKey()).append(" CONTAINS '").append(propertyFilterConfig.getValue()).append("'");
                    break;
                case NOT_LIKE:
                    sbCypher.append(" NOT ").append("n.").append(propertyFilterConfig.getPropertyKey()).append(" CONTAINS '").append(propertyFilterConfig.getValue()).append("'");
                    break;
                default:
                    break;
            }
        }
        sbCypher.append(" RETURN n");
        GraphNeo4jVO graph = graphProcessor.getNodeByCypher(sbCypher.toString());
        return graph.getNodes();
    }

    @Override
    public List<ExpendMenuVO> listExpendMenu(String[] nodeIds, Integer taskId) {
        List<ExpendMenuVO> retValue = new ArrayList<>();

        String cypher = null;
        if (nodeIds.length == 1) {
            cypher = "Match p=(m{taskId:" + taskId + ", id:'" + nodeIds[0] + "'})-[]-(n{taskId:" + taskId + "}) WHERE n.mergeType is null or  n.mergeType <> 'eventNode' RETURN labels(n) as labels";
        } else if (nodeIds.length > 1) {
            StringBuilder sbCypher = new StringBuilder();
            StringBuilder sbIds = new StringBuilder();
            sbIds.append("[");
            for (int i = 0; i < nodeIds.length; i++) {
                sbIds.append("'").append(nodeIds[i]).append("'");
                if (i < nodeIds.length - 1) {
                    sbIds.append(",");
                }
            }
            sbIds.append("]");
            sbCypher.append("match p=(n1)-[]-(n)-[]-(n2) where n1.taskId = ")
                    .append(taskId)
                    .append(" and n2.taskId = ")
                    .append(taskId)
                    .append(" and n1.id in ")
                    .append(sbIds)
                    .append(" and n2.id in ")
                    .append(sbIds)
                    .append(" and n1<>n2 and (n.mergeType is null or n.mergeType <> 'eventNode')  return labels(n) as labels");

            cypher = sbCypher.toString();
        }

        if (!StringUtils.isEmpty(cypher)) {
            Set<String> labels = graphProcessor.getLabelsByCypher(cypher);
            for(NodeTypeEnum nodeTypeEnum : NodeTypeEnum.values()) {
                Boolean access = labels.contains(nodeTypeEnum.getName());
                ExpendMenuVO expendMenu = new ExpendMenuVO(nodeTypeEnum.getCode(), nodeTypeEnum.getName(), access);
                retValue.add(expendMenu);
            }
        }
        return retValue;
    }

    @Override
    public List<GraphNeo4jVO.Link> listLinkByNodeId(String nodeId, Integer taskId) {
        String cypher = "match p=(n)-[r]-() where n.taskId =" + taskId + " and n.id = '" + nodeId + "' return p";

        GraphNeo4jVO graph = graphProcessor.getPathByCypher2(cypher);
        return graph.getLinks();
    }



   /* private GraphNeo4jVO getPath(PathAnalyzeVO pathAnalyzeVO, Integer depth) {
        String cypher;
        if (pathAnalyzeVO.getIsShortest()) {
            cypher = "match p=allShortestPaths((m{taskId:" + pathAnalyzeVO.getTaskId() +
                    "})-[*.." + depth +
                    "]-(n{taskId:" + pathAnalyzeVO.getTaskId() + "})) where m.id='"
                    + pathAnalyzeVO.getListIds().get(0) +
                    "' and n.id='" + pathAnalyzeVO.getListIds().get(1) + "' return p";
        } else {
            cypher = "match p=(m{taskId:" + pathAnalyzeVO.getTaskId() +
                    "})-[*.." + depth +
                    "]-(n{taskId:" + pathAnalyzeVO.getTaskId() + "}) where m.id='"
                    + pathAnalyzeVO.getListIds().get(0) +
                    "' and n.id='" + pathAnalyzeVO.getListIds().get(1) + "' return p";
        }

        return graphProcessor.getPathByCypher(cypher);
    }*/


}
